/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * TimesheetEditor.java
 *
 * Created on 07-Nov-2010, 21:07:05
 */

package TimeRecording.forms;

import TimeRecording.TimeRecordingView;
import TimeRecording.business.AbsenceGrid;
import TimeRecording.business.DaisyChainUtilities;
import TimeRecording.business.DateRange;
import TimeRecording.enums.AuthorisationLevel;
import TimeRecording.enums.PartOfDay;
import TimeRecording.enums.WeeklyTimeStatus;
import TimeRecording.forms.renderers.HoursCellRenderer;
import TimeRecording.tables.AllocationOfHours;
import TimeRecording.tables.Employee;
import TimeRecording.tables.HolidayRequest;
import TimeRecording.tables.PublicHoliday;
import TimeRecording.tables.SicknessReport;
import TimeRecording.tables.WeeklyTime;
import TimeRecording.tables.WeeklyTimePK;
import TimeRecording.widgets.TimeTextField;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import org.jdesktop.application.Action;
import org.jdesktop.observablecollections.ObservableCollections;

/**
 *
 * @author david
 */
public class TimesheetEditor extends javax.swing.JDialog
        implements PropertyChangeListener{

    private final static Logger LOGGER = Logger.getLogger(
            TimesheetEditor.class.getName());

    private TimeRecordingView myParent;
    private EntityManager passedEntityManager;
    private GregorianCalendar weekCommencingCalendar;
    
    private AbsenceGrid absenceGrid = null;

    // <editor-fold defaultstate="collapsed" desc="Getters & Setters">
    public boolean editsMade;

    public boolean getEditsMade() {
        return editsMade;
    }

    public void setEditsMade(boolean editsMade) {
        boolean oldEditsMade = this.editsMade;
        this.editsMade = editsMade;
        firePropertyChange("editsMade", oldEditsMade, editsMade);
    }

    public Date weekCommencing;
    
    public Date getWeekCommencing() {
        return this.weekCommencing;
    }
    
    public void setWeekCommencing(Date weekCommencing) {
        Date oldWeekCommencing = this.weekCommencing;
        this.weekCommencing = weekCommencing;
        firePropertyChange("weekCommencing", oldWeekCommencing, weekCommencing);        
    }

    public boolean includeDeletedEmployees;

    public boolean isIncludeDeletedEmployees() {
        return includeDeletedEmployees;
    }

    public void setIncludeDeletedEmployees(boolean includeDeletedEmployees) {
        boolean oldIncludeDeletedEmployees = this.includeDeletedEmployees;
        this.includeDeletedEmployees = includeDeletedEmployees;
        firePropertyChange("includeDeletedEmployees", oldIncludeDeletedEmployees, includeDeletedEmployees);
    }

    public WeeklyTime selectedTimesheet;

    public WeeklyTime getSelectedTimesheet() {
        return selectedTimesheet;
    }
    
    public void setSelectedTimesheet(WeeklyTime selectedTimesheet) {
        WeeklyTime oldSelectedTimesheet = this.selectedTimesheet;
        this.selectedTimesheet = selectedTimesheet;
        firePropertyChange("selectedTimesheet", oldSelectedTimesheet, selectedTimesheet);
        setTimesheetSelected(selectedTimesheet != null);
    }

    protected boolean timesheetSelected = false;

    public boolean isTimesheetSelected() {
	return timesheetSelected;
    }

    public void setTimesheetSelected(boolean timesheetSelected) {
        boolean oldTimesheetSelected = this.timesheetSelected;
        this.timesheetSelected = timesheetSelected;
        firePropertyChange("timesheetSelected", oldTimesheetSelected, timesheetSelected);
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="TimesheetCellRenderer">
    private class TimesheetCellRenderer extends HoursCellRenderer {

        private Color defaultBackground = UIManager.
                getLookAndFeelDefaults().
                getColor("Table.background");
        private Color selectedBackground = UIManager.
                getLookAndFeelDefaults().
                getColor("Table.selectionBackground");
        private Border selectedBorder = UIManager.
                getLookAndFeelDefaults().
                getBorder("Table.focusCellHighlightBorder");

        private Color VERY_LIGHT_GREY = new Color(0xE0, 0xE0, 0xE0);
        private final static int X_BORDER = 4;
        private final static int Y_BORDER = 3;

        private boolean hasFocus;
        private Color defaultColour = null;
        private Color topLeftColour = null;
        private Color bottomRightColour = null;
        private Color backColour = null;
        private Color textColour;
        private Double value;

        private GregorianCalendar cal = new GregorianCalendar();
        private Font font;
        private FontMetrics fontMetrics;

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {

            this.hasFocus = hasFocus;
            
            AllocationOfHours cell = (AllocationOfHours)value;

            // Work out the number of hours
            if (cell.getEntered() > 0) {
                this.value = cell.getEntered();

            } else if (cell.getClocked() > 0) {
                this.value = cell.getClocked();

            } else if (cell.getEstimated() > 0) {
                this.value = cell.getEstimated();

            } else {
                this.value = 0.0;
            }

            // Work out a key into the absence grid (employee + date)
            WeeklyTime weeklyTime = timesheetList.get(row);
            Employee employee = weeklyTime.getEmployee();

            int dayOffset = column - 1;
            cal.setTime(weeklyTime.getWeeklyTimePK().getWeekCommencing());
            cal.add(GregorianCalendar.DATE, dayOffset);
            Date date = cal.getTime();

            // Get details of any absences
            PartOfDay sickness = absenceGrid.getSicknessCell(employee, date);
            PartOfDay holiday = absenceGrid.getHolidayCell(employee, date);

            if (isSelected) {
                defaultColour = selectedBackground;
            } else {
                defaultColour = defaultBackground;
            }

            // Work out the colour of the top left hand corner
            if (sickness== PartOfDay.AM || sickness == PartOfDay.ALL_DAY) {
                topLeftColour = DaisyChainUtilities.BRIGHT_RED;
            } else if (holiday == PartOfDay.AM || holiday == PartOfDay.ALL_DAY) {
                topLeftColour = DaisyChainUtilities.BRIGHT_GREEN;
            } else {
                topLeftColour = defaultColour;
            }

            if (topLeftColour == DaisyChainUtilities.BRIGHT_GREEN &&
                    absenceGrid.isPublicHoliday(date)) {
                topLeftColour = Color.YELLOW;
            }

            // Work out the colour of the bottom right hand corner
            if (sickness == PartOfDay.PM || sickness == PartOfDay.ALL_DAY) {
                bottomRightColour = DaisyChainUtilities.BRIGHT_RED;
            } else if (holiday == PartOfDay.PM || holiday == PartOfDay.ALL_DAY) {
                bottomRightColour = DaisyChainUtilities.BRIGHT_GREEN;
            } else {
                bottomRightColour = defaultColour;
            }

            if (bottomRightColour == DaisyChainUtilities.BRIGHT_GREEN &&
                    absenceGrid.isPublicHoliday(date)) {
                bottomRightColour = Color.YELLOW;
            }

            // Work out the colour of the text:
            //  * red = entered via clock
            //  * blue = entered via timesheet
            //  * magenta = entered via clock but overriden by timesheet
            //  * black = other (pretty much redundant as no text to output)

            backColour = defaultColour;
            if (cell.getEntered() > 0) {
                if (cell.getClocked() > 0) {
                    textColour = Color.MAGENTA;
                } else {
                    textColour = Color.BLUE;
                }
            } else {
                if (cell.getClocked() > 0) {
                    textColour = Color.RED;
                } else {
                    textColour = Color.BLACK;
                    if (cell.getEstimated() > 0) {
                        backColour = VERY_LIGHT_GREY;
                    }
                }
            }

            return this;
        }

        @Override
        public void paint(Graphics g) {

            super.paint(g);

            Color savedColour = g.getColor();
            Font savedFont = g.getFont();

            try {
                int cellLeft = 0;
                int cellTop = 0;
                int cellRight = getWidth();
                int cellBottom = getHeight();

                // Fill the background with our chosen colour
                g.setColor(backColour);
                g.fillRect(cellLeft, cellTop, cellRight, cellBottom);

                // Fill the top left of our box in the chosen colour
                if (topLeftColour != defaultColour) {
                    g.setColor(topLeftColour);
                    g.fillPolygon(
                            new int[] { cellLeft, cellRight , cellLeft },
                            new int[] { cellTop, cellTop, cellBottom }, 3 );
                }

                // Fill the bottom right of our box in the chosen colour
                if (bottomRightColour != defaultColour) {
                    g.setColor(bottomRightColour);
                    g.fillPolygon(
                            new int[] { cellLeft, cellRight, cellRight },
                            new int[] { cellBottom, cellBottom, cellTop }, 3 );
                }

                // re-colour the centre of the cell
                g.setColor(backColour);
                g.fillRect(cellLeft + X_BORDER, cellTop + Y_BORDER,
                        cellRight - X_BORDER * 2, cellBottom - Y_BORDER * 2);

                // Get font details and font metrics
                // unless we've already got them
                if (font == null) {
                    font = (Font)UIManager.get("Table.font");
                }
                if (fontMetrics == null) {
                    fontMetrics = g.getFontMetrics(font);
                }

                // How much space is needed by our text?
                String text = formatValue(value);

                g.setFont(font);
                Rectangle2D bounds = fontMetrics.getStringBounds(text, g);

                int spareX = (int)(getWidth() - bounds.getWidth());
                int spareY = (int)(getHeight() - bounds.getHeight());

                int textX = cellLeft + spareX / 2;
                int textY = cellBottom - spareY / 2 - fontMetrics.getDescent();

                g.setColor(textColour);
                g.drawString(text, textX, textY);

                if (hasFocus) {
                    selectedBorder.paintBorder(this, g, 0, 0, getWidth(), getHeight());
                }

            } finally {
                g.setColor(savedColour);
                g.setFont(savedFont);
            }
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="TimesheetCellEditor">
    private class TimesheetCellEditor extends AbstractCellEditor implements TableCellEditor {

        private WeeklyTime weeklyTime;
        private AllocationOfHours value;
        private TimeTextField component = new TimeTextField();

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value,
                boolean isSelected, int row, int column) {

            weeklyTime = timesheetList.get(row);
            this.value = (AllocationOfHours)value;

            if (this.value.getEntered() != 0.0) {
                component.setText(Double.toString(this.value.getEntered()));

            } else if (this.value.getClocked() != 0.0) {
                component.setText(Double.toString(this.value.getClocked()));

            } else if (this.value.getEstimated() != 0.0) {
                component.setText(Double.toString(this.value.getEstimated()));

            } else {
                component.setText("");
            }

            component.selectAll();

            return component;
        }
        
        @Override
        public Object getCellEditorValue() {
            
            if (component.getTimeValue() == null) {
                value.setEntered(0.0);

            } else if ((Double)component.getTimeValue() == 0.0) {
                value.setEntered(AllocationOfHours.ZERO_TIME);

            } else {
                value.setEntered((Double)component.getTimeValue());
            }
            
            weeklyTime.setTotal(null);
            return value;
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="TimsheetStatusRenderer">
    private class TimesheetStatusRenderer extends JComboBox implements TableCellRenderer {

        public TimesheetStatusRenderer() {
            super(WeeklyTimeStatus.values());
        }

        public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {

            if (isSelected) {
                setForeground(table.getSelectionForeground());
                setBackground(table.getSelectionBackground());
            } else {
                setForeground(table.getForeground());
                setBackground(table.getBackground());
            }

            setSelectedItem(value);
            return this;
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="TimesheetStatusEditor">
    private class TimesheetStatusEditor extends DefaultCellEditor {
        public TimesheetStatusEditor() {
            super(new JComboBox(WeeklyTimeStatus.values()));
        }
    }
    // </editor-fold>

    private TimesheetStatusRenderer statusRenderer = new TimesheetStatusRenderer();
    private TimesheetStatusEditor statusEditor = new TimesheetStatusEditor();

    private TimesheetCellRenderer cellRenderer = new TimesheetCellRenderer();
    private TimesheetCellEditor cellEditor = new TimesheetCellEditor();

    private HoursCellRenderer totalRenderer = new HoursCellRenderer();

    /** Creates new form TimesheetEditor */
    public TimesheetEditor(TimeRecordingView parent,
            EntityManager entityManager, boolean modal) {
        
        super(parent.getFrame(), modal);

        myParent = parent;
        this.passedEntityManager = entityManager;
        
        initComponents();

        timesheetTable.getColumnModel().getColumn(1).setCellEditor(cellEditor);
        timesheetTable.getColumnModel().getColumn(2).setCellEditor(cellEditor);
        timesheetTable.getColumnModel().getColumn(3).setCellEditor(cellEditor);
        timesheetTable.getColumnModel().getColumn(4).setCellEditor(cellEditor);
        timesheetTable.getColumnModel().getColumn(5).setCellEditor(cellEditor);
        timesheetTable.getColumnModel().getColumn(6).setCellEditor(cellEditor);
        timesheetTable.getColumnModel().getColumn(7).setCellEditor(cellEditor);

        timesheetTable.getColumnModel().getColumn(11).setCellEditor(statusEditor);

        weekCommencingCalendar = DaisyChainUtilities.
                firstDayOfWeek((GregorianCalendar)null);
        
        setWeekCommencing(weekCommencingCalendar.getTime());
        setIncludeDeletedEmployees(false);
        loadTimesheets();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        entityManager = passedEntityManager;
        getTimesheets = entityManager.createNamedQuery("WeeklyTime.findByWeekCommencing");
        timesheetList = ObservableCollections.observableList(new ArrayList<WeeklyTime>());
        getEmployees = Employee.getEmployeesQuery(entityManager);
        employeeList = java.beans.Beans.isDesignTime() ? java.util.Collections.emptyList() : getEmployees.getResultList();
        unitsButtonGroup = new javax.swing.ButtonGroup();
        weekSelectionPanel = new javax.swing.JPanel();
        previousButton = new javax.swing.JButton();
        weekCommencingLabel = new javax.swing.JLabel();
        weekCommencingField = new javax.swing.JFormattedTextField();
        nextButton = new javax.swing.JButton();
        includeDeletedEmployeesButton = new javax.swing.JToggleButton();
        saveButton = new javax.swing.JButton();
        hoursAndMinutesToggle = new javax.swing.JToggleButton();
        hoursToggle = new javax.swing.JToggleButton();
        timesheetPane = new javax.swing.JScrollPane();
        timesheetTable = new javax.swing.JTable();
        keyPanel = new javax.swing.JPanel();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        jLabel3 = new javax.swing.JLabel();
        jLabel5 = new javax.swing.JLabel();
        jLabel6 = new javax.swing.JLabel();
        jLabel4 = new javax.swing.JLabel();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(TimeRecording.TimeRecordingApp.class).getContext().getResourceMap(TimesheetEditor.class);
        setTitle(resourceMap.getString("Form.title")); // NOI18N
        setName("Form"); // NOI18N
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowActivated(java.awt.event.WindowEvent evt) {
                formWindowActivated(evt);
            }
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });
        getContentPane().setLayout(new java.awt.GridBagLayout());

        weekSelectionPanel.setName("weekSelectionPanel"); // NOI18N

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(TimeRecording.TimeRecordingApp.class).getContext().getActionMap(TimesheetEditor.class, this);
        previousButton.setAction(actionMap.get("previousWeek")); // NOI18N
        previousButton.setText(resourceMap.getString("previousButton.text")); // NOI18N
        previousButton.setMargin(new java.awt.Insets(2, 2, 2, 2));
        previousButton.setName("previousButton"); // NOI18N
        previousButton.setPreferredSize(new java.awt.Dimension(32, 20));
        weekSelectionPanel.add(previousButton);

        weekCommencingLabel.setText(resourceMap.getString("weekCommencingLabel.text")); // NOI18N
        weekCommencingLabel.setName("weekCommencingLabel"); // NOI18N
        weekSelectionPanel.add(weekCommencingLabel);

        weekCommencingField.setEditable(false);
        weekCommencingField.setFormatterFactory(new javax.swing.text.DefaultFormatterFactory(new javax.swing.text.DateFormatter()));
        weekCommencingField.setText(resourceMap.getString("weekCommencingField.text")); // NOI18N
        weekCommencingField.setName("weekCommencingField"); // NOI18N
        weekCommencingField.setPreferredSize(new java.awt.Dimension(150, 20));

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${weekCommencing}"), weekCommencingField, org.jdesktop.beansbinding.BeanProperty.create("value"));
        bindingGroup.addBinding(binding);

        weekSelectionPanel.add(weekCommencingField);

        nextButton.setAction(actionMap.get("nextWeek")); // NOI18N
        nextButton.setText(resourceMap.getString("nextButton.text")); // NOI18N
        nextButton.setMargin(new java.awt.Insets(2, 2, 2, 2));
        nextButton.setName("nextButton"); // NOI18N
        nextButton.setPreferredSize(new java.awt.Dimension(32, 20));
        weekSelectionPanel.add(nextButton);

        includeDeletedEmployeesButton.setAction(actionMap.get("toggleIncludeDeletedEmployees")); // NOI18N
        includeDeletedEmployeesButton.setText(resourceMap.getString("includeDeletedEmployeesButton.text")); // NOI18N
        includeDeletedEmployeesButton.setName("includeDeletedEmployeesButton"); // NOI18N

        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${includeDeletedEmployees}"), includeDeletedEmployeesButton, org.jdesktop.beansbinding.BeanProperty.create("selected"));
        bindingGroup.addBinding(binding);

        weekSelectionPanel.add(includeDeletedEmployeesButton);

        saveButton.setAction(actionMap.get("saveTimesheets")); // NOI18N
        saveButton.setText(resourceMap.getString("saveButton.text")); // NOI18N
        saveButton.setName("saveButton"); // NOI18N
        weekSelectionPanel.add(saveButton);

        hoursAndMinutesToggle.setAction(actionMap.get("displayAsHoursAndMinutes")); // NOI18N
        unitsButtonGroup.add(hoursAndMinutesToggle);
        hoursAndMinutesToggle.setSelected(true);
        hoursAndMinutesToggle.setText(resourceMap.getString("hoursAndMinutesToggle.text")); // NOI18N
        hoursAndMinutesToggle.setMargin(new java.awt.Insets(2, 2, 2, 2));
        hoursAndMinutesToggle.setMaximumSize(new java.awt.Dimension(111, 23));
        hoursAndMinutesToggle.setMinimumSize(new java.awt.Dimension(40, 23));
        hoursAndMinutesToggle.setName("hoursAndMinutesToggle"); // NOI18N
        hoursAndMinutesToggle.setPreferredSize(new java.awt.Dimension(70, 23));
        weekSelectionPanel.add(hoursAndMinutesToggle);

        hoursToggle.setAction(actionMap.get("displayAsHours")); // NOI18N
        unitsButtonGroup.add(hoursToggle);
        hoursToggle.setText(resourceMap.getString("hoursToggle.text")); // NOI18N
        hoursToggle.setMargin(new java.awt.Insets(2, 2, 2, 2));
        hoursToggle.setMaximumSize(new java.awt.Dimension(111, 23));
        hoursToggle.setMinimumSize(new java.awt.Dimension(40, 23));
        hoursToggle.setName("hoursToggle"); // NOI18N
        hoursToggle.setPreferredSize(new java.awt.Dimension(70, 23));
        weekSelectionPanel.add(hoursToggle);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        getContentPane().add(weekSelectionPanel, gridBagConstraints);

        timesheetPane.setBorder(javax.swing.BorderFactory.createCompoundBorder(javax.swing.BorderFactory.createEmptyBorder(4, 8, 4, 8), javax.swing.BorderFactory.createEtchedBorder()));
        timesheetPane.setName("timesheetPane"); // NOI18N

        timesheetTable.setAutoCreateRowSorter(true);
        timesheetTable.setName("timesheetTable"); // NOI18N
        timesheetTable.setRowHeight(24);
        timesheetTable.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);

        org.jdesktop.swingbinding.JTableBinding jTableBinding = org.jdesktop.swingbinding.SwingBindings.createJTableBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, timesheetList, timesheetTable);
        org.jdesktop.swingbinding.JTableBinding.ColumnBinding columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${employee}"));
        columnBinding.setColumnName("Employee");
        columnBinding.setColumnClass(TimeRecording.tables.Employee.class);
        columnBinding.setEditable(false);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${monday}"));
        columnBinding.setColumnName("Monday");
        columnBinding.setColumnClass(TimeRecording.tables.AllocationOfHours.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${tuesday}"));
        columnBinding.setColumnName("Tuesday");
        columnBinding.setColumnClass(TimeRecording.tables.AllocationOfHours.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${wednesday}"));
        columnBinding.setColumnName("Wednesday");
        columnBinding.setColumnClass(TimeRecording.tables.AllocationOfHours.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${thursday}"));
        columnBinding.setColumnName("Thursday");
        columnBinding.setColumnClass(TimeRecording.tables.AllocationOfHours.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${friday}"));
        columnBinding.setColumnName("Friday");
        columnBinding.setColumnClass(TimeRecording.tables.AllocationOfHours.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${saturday}"));
        columnBinding.setColumnName("Saturday");
        columnBinding.setColumnClass(TimeRecording.tables.AllocationOfHours.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${sunday}"));
        columnBinding.setColumnName("Sunday");
        columnBinding.setColumnClass(TimeRecording.tables.AllocationOfHours.class);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${total}"));
        columnBinding.setColumnName("Total");
        columnBinding.setColumnClass(Double.class);
        columnBinding.setEditable(false);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${totalInclHoliday}"));
        columnBinding.setColumnName("Total Incl Holiday");
        columnBinding.setColumnClass(Double.class);
        columnBinding.setEditable(false);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${administrator}"));
        columnBinding.setColumnName("Administrator");
        columnBinding.setColumnClass(TimeRecording.tables.Employee.class);
        columnBinding.setEditable(false);
        columnBinding = jTableBinding.addColumnBinding(org.jdesktop.beansbinding.ELProperty.create("${status}"));
        columnBinding.setColumnName("Status");
        columnBinding.setColumnClass(TimeRecording.enums.WeeklyTimeStatus.class);
        bindingGroup.addBinding(jTableBinding);
        jTableBinding.bind();binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ_WRITE, this, org.jdesktop.beansbinding.ELProperty.create("${selectedTimesheet}"), timesheetTable, org.jdesktop.beansbinding.BeanProperty.create("selectedElement"));
        bindingGroup.addBinding(binding);

        timesheetPane.setViewportView(timesheetTable);
        timesheetTable.getColumnModel().getColumn(0).setPreferredWidth(120);
        timesheetTable.getColumnModel().getColumn(0).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title0")); // NOI18N
        timesheetTable.getColumnModel().getColumn(1).setPreferredWidth(50);
        timesheetTable.getColumnModel().getColumn(1).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title1")); // NOI18N
        timesheetTable.getColumnModel().getColumn(1).setCellRenderer(cellRenderer);
        timesheetTable.getColumnModel().getColumn(2).setPreferredWidth(50);
        timesheetTable.getColumnModel().getColumn(2).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title2")); // NOI18N
        timesheetTable.getColumnModel().getColumn(2).setCellRenderer(cellRenderer);
        timesheetTable.getColumnModel().getColumn(3).setPreferredWidth(50);
        timesheetTable.getColumnModel().getColumn(3).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title3")); // NOI18N
        timesheetTable.getColumnModel().getColumn(3).setCellRenderer(cellRenderer);
        timesheetTable.getColumnModel().getColumn(4).setPreferredWidth(50);
        timesheetTable.getColumnModel().getColumn(4).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title4")); // NOI18N
        timesheetTable.getColumnModel().getColumn(4).setCellRenderer(cellRenderer);
        timesheetTable.getColumnModel().getColumn(5).setPreferredWidth(50);
        timesheetTable.getColumnModel().getColumn(5).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title5")); // NOI18N
        timesheetTable.getColumnModel().getColumn(5).setCellRenderer(cellRenderer);
        timesheetTable.getColumnModel().getColumn(6).setPreferredWidth(50);
        timesheetTable.getColumnModel().getColumn(6).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title6")); // NOI18N
        timesheetTable.getColumnModel().getColumn(6).setCellRenderer(cellRenderer);
        timesheetTable.getColumnModel().getColumn(7).setPreferredWidth(50);
        timesheetTable.getColumnModel().getColumn(7).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title7")); // NOI18N
        timesheetTable.getColumnModel().getColumn(7).setCellRenderer(cellRenderer);
        timesheetTable.getColumnModel().getColumn(8).setPreferredWidth(50);
        timesheetTable.getColumnModel().getColumn(8).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title10")); // NOI18N
        timesheetTable.getColumnModel().getColumn(8).setCellRenderer(totalRenderer);
        timesheetTable.getColumnModel().getColumn(9).setPreferredWidth(90);
        timesheetTable.getColumnModel().getColumn(9).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title11")); // NOI18N
        timesheetTable.getColumnModel().getColumn(9).setCellRenderer(totalRenderer);
        timesheetTable.getColumnModel().getColumn(10).setPreferredWidth(120);
        timesheetTable.getColumnModel().getColumn(10).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title8")); // NOI18N
        timesheetTable.getColumnModel().getColumn(11).setHeaderValue(resourceMap.getString("timesheetTable.columnModel.title9")); // NOI18N

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        getContentPane().add(timesheetPane, gridBagConstraints);

        keyPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(resourceMap.getString("keyPanel.border.title"))); // NOI18N
        keyPanel.setMaximumSize(new java.awt.Dimension(200, 100));
        keyPanel.setMinimumSize(new java.awt.Dimension(200, 72));
        keyPanel.setName("keyPanel"); // NOI18N
        keyPanel.setPreferredSize(new java.awt.Dimension(1238, 72));
        keyPanel.setLayout(new java.awt.GridBagLayout());

        jLabel1.setBackground(resourceMap.getColor("jLabel1.background")); // NOI18N
        jLabel1.setForeground(resourceMap.getColor("jLabel1.foreground")); // NOI18N
        jLabel1.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
        jLabel1.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));
        jLabel1.setMaximumSize(new java.awt.Dimension(300, 20));
        jLabel1.setMinimumSize(new java.awt.Dimension(300, 20));
        jLabel1.setName("jLabel1"); // NOI18N
        jLabel1.setOpaque(true);
        jLabel1.setPreferredSize(new java.awt.Dimension(240, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 2, 4);
        keyPanel.add(jLabel1, gridBagConstraints);

        jLabel2.setBackground(resourceMap.getColor("jLabel2.background")); // NOI18N
        jLabel2.setForeground(resourceMap.getColor("jLabel2.foreground")); // NOI18N
        jLabel2.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel2.setText(resourceMap.getString("jLabel2.text")); // NOI18N
        jLabel2.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));
        jLabel2.setMaximumSize(new java.awt.Dimension(300, 20));
        jLabel2.setMinimumSize(new java.awt.Dimension(300, 20));
        jLabel2.setName("jLabel2"); // NOI18N
        jLabel2.setOpaque(true);
        jLabel2.setPreferredSize(new java.awt.Dimension(240, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 2, 4);
        keyPanel.add(jLabel2, gridBagConstraints);

        jLabel3.setBackground(resourceMap.getColor("jLabel3.background")); // NOI18N
        jLabel3.setForeground(resourceMap.getColor("jLabel3.foreground")); // NOI18N
        jLabel3.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel3.setText(resourceMap.getString("jLabel3.text")); // NOI18N
        jLabel3.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));
        jLabel3.setMaximumSize(new java.awt.Dimension(300, 20));
        jLabel3.setMinimumSize(new java.awt.Dimension(300, 20));
        jLabel3.setName("jLabel3"); // NOI18N
        jLabel3.setOpaque(true);
        jLabel3.setPreferredSize(new java.awt.Dimension(240, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 2, 4);
        keyPanel.add(jLabel3, gridBagConstraints);

        jLabel5.setBackground(resourceMap.getColor("jLabel5.background")); // NOI18N
        jLabel5.setForeground(resourceMap.getColor("jLabel5.foreground")); // NOI18N
        jLabel5.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel5.setText(resourceMap.getString("jLabel5.text")); // NOI18N
        jLabel5.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));
        jLabel5.setMaximumSize(new java.awt.Dimension(300, 20));
        jLabel5.setMinimumSize(new java.awt.Dimension(300, 20));
        jLabel5.setName("jLabel5"); // NOI18N
        jLabel5.setOpaque(true);
        jLabel5.setPreferredSize(new java.awt.Dimension(240, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 4, 4, 4);
        keyPanel.add(jLabel5, gridBagConstraints);

        jLabel6.setBackground(resourceMap.getColor("jLabel6.background")); // NOI18N
        jLabel6.setForeground(resourceMap.getColor("jLabel6.foreground")); // NOI18N
        jLabel6.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel6.setText(resourceMap.getString("jLabel6.text")); // NOI18N
        jLabel6.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));
        jLabel6.setMaximumSize(new java.awt.Dimension(300, 20));
        jLabel6.setMinimumSize(new java.awt.Dimension(300, 20));
        jLabel6.setName("jLabel6"); // NOI18N
        jLabel6.setOpaque(true);
        jLabel6.setPreferredSize(new java.awt.Dimension(240, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 4, 4, 4);
        keyPanel.add(jLabel6, gridBagConstraints);

        jLabel4.setBackground(resourceMap.getColor("jLabel4.background")); // NOI18N
        jLabel4.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        jLabel4.setText(resourceMap.getString("jLabel4.text")); // NOI18N
        jLabel4.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.RAISED));
        jLabel4.setMaximumSize(new java.awt.Dimension(300, 20));
        jLabel4.setMinimumSize(new java.awt.Dimension(300, 20));
        jLabel4.setName("jLabel4"); // NOI18N
        jLabel4.setOpaque(true);
        jLabel4.setPreferredSize(new java.awt.Dimension(240, 20));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(2, 4, 4, 4);
        keyPanel.add(jLabel4, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        getContentPane().add(keyPanel, gridBagConstraints);

        bindingGroup.bind();

        java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
        setBounds((screenSize.width-939)/2, (screenSize.height-490)/2, 939, 490);
    }// </editor-fold>//GEN-END:initComponents

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        // Save (or discard) any changes that might have been made
        if  (!checkSaveTimesheets()) {
            return;
        }
        myParent.hideTimesheetsDialog();
    }//GEN-LAST:event_formWindowClosing

    private void formWindowActivated(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowActivated
        loadTimesheets();
    }//GEN-LAST:event_formWindowActivated

    @Action
    public void previousWeek() {

        // Save (or discard) any changes that might have been made
        if  (!checkSaveTimesheets()) {
            return;
        }

        weekCommencingCalendar.add(GregorianCalendar.DATE, -7);
        setWeekCommencing(weekCommencingCalendar.getTime());
        loadTimesheets();
    }

    @Action
    public void nextWeek() {

        // Save (or discard) any changes that might have been made
        if  (!checkSaveTimesheets()) {
            return;
        }

        weekCommencingCalendar.add(GregorianCalendar.DATE, 7);
        setWeekCommencing(weekCommencingCalendar.getTime());
        loadTimesheets();
    }

    @SuppressWarnings("unchecked")
    private void loadTimesheets() {

        GregorianCalendar cal = new GregorianCalendar();
        cal.setTime(weekCommencing);

        DateRange week = new DateRange(
                DaisyChainUtilities.firstDayOfWeek(cal).getTime(),
                DaisyChainUtilities.lastDayOfWeek(cal).getTime());

        Collection<HolidayRequest> holidays = HolidayRequest.
                getHolidaysForDateRange(entityManager, week).getResultList();

        Collection<SicknessReport> sickness = SicknessReport.
                getSicknessReportsForDateRange(entityManager, week).getResultList();

        Collection<PublicHoliday> publicHolidays = PublicHoliday.
                getHolidaysForDateRange(entityManager, week);

        absenceGrid = new AbsenceGrid(holidays, sickness, publicHolidays);

        getTimesheets.setParameter("weekCommencing", weekCommencing);
        List<WeeklyTime> timesheets = getTimesheets.getResultList();

        // Decouple any listeners before loading up the next set of data
        for (WeeklyTime timesheet : timesheetList) {
            timesheet.removePropertyChangeListener(this);
            timesheet.getMonday().removePropertyChangeListener(this);
            timesheet.getTuesday().removePropertyChangeListener(this);
            timesheet.getWednesday().removePropertyChangeListener(this);
            timesheet.getThursday().removePropertyChangeListener(this);
            timesheet.getFriday().removePropertyChangeListener(this);
            timesheet.getSaturday().removePropertyChangeListener(this);
            timesheet.getSunday().removePropertyChangeListener(this);
        }
        timesheetList.clear();

        for (Employee employee : employeeList) {

            // Skip any deleted employees if they're filtered out
            if (!includeDeletedEmployees && employee.getAuthorisationLevel()
                    == AuthorisationLevel.DELETED) {
                continue;
            }
            
            // Try to find a weekly timesheet            
            boolean timesheetFound = false;
            for (WeeklyTime timesheet : timesheets) {
                if (timesheet.getEmployee() == employee) {
                    addTimesheetToList(timesheet);
                    timesheetFound = true;
                }
            }
            
            // If we've added a persistent timesheet to our list then advance
            if (timesheetFound) {
                continue;
            }
            
            // Okay, there's no timesheet yet so we'll new one up and add it to
            // our list.
            WeeklyTime timesheet = new WeeklyTime();
            timesheet.setEmployee(employee);
            timesheet.setWeeklyTimePK(new WeeklyTimePK(
                    employee.getEmployeeId(), weekCommencing));

            addTimesheetToList(timesheet);
        }

        setEditsMade(false);
    }

    private void addTimesheetToList(WeeklyTime timesheet) {
        timesheet.addPropertyChangeListener(this);
        timesheet.getMonday().addPropertyChangeListener(this);
        timesheet.getTuesday().addPropertyChangeListener(this);
        timesheet.getWednesday().addPropertyChangeListener(this);
        timesheet.getThursday().addPropertyChangeListener(this);
        timesheet.getFriday().addPropertyChangeListener(this);
        timesheet.getSaturday().addPropertyChangeListener(this);
        timesheet.getSunday().addPropertyChangeListener(this);
        timesheetList.add(timesheet);
    }

    @Action
    public void toggleIncludeDeletedEmployees() {

        // Save (or discard) any changes that might have been made
        if  (!checkSaveTimesheets()) {
            return;
        }
        
        loadTimesheets();
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private java.util.List<TimeRecording.tables.Employee> employeeList;
    private javax.persistence.EntityManager entityManager;
    private javax.persistence.Query getEmployees;
    private javax.persistence.Query getTimesheets;
    private javax.swing.JToggleButton hoursAndMinutesToggle;
    private javax.swing.JToggleButton hoursToggle;
    private javax.swing.JToggleButton includeDeletedEmployeesButton;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JPanel keyPanel;
    private javax.swing.JButton nextButton;
    private javax.swing.JButton previousButton;
    private javax.swing.JButton saveButton;
    private java.util.List<WeeklyTime> timesheetList;
    private javax.swing.JScrollPane timesheetPane;
    private javax.swing.JTable timesheetTable;
    private javax.swing.ButtonGroup unitsButtonGroup;
    private javax.swing.JFormattedTextField weekCommencingField;
    private javax.swing.JLabel weekCommencingLabel;
    private javax.swing.JPanel weekSelectionPanel;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables

    public void propertyChange(PropertyChangeEvent evt) {

        Object source = evt.getSource();
        WeeklyTime timesheet = getSelectedTimesheet();
        if (!isVisible() || timesheet == null) {
            return;
        }

        if (source == timesheet.getMonday() ||
                source == timesheet.getTuesday() ||
                source == timesheet.getWednesday() ||
                source == timesheet.getThursday() ||
                source == timesheet.getFriday() ||
                source == timesheet.getSaturday() ||
                source == timesheet.getSunday()) {
            
            setEditsMade(true);
        }
    }

    private boolean checkSaveTimesheets() {

        // If no changes have been made then just return true
        if (!this.getEditsMade()) {
            return true;
        }

        int option = JOptionPane.showConfirmDialog(this,
                "Changes have been made, do you wish to save them",
                "Save changed timesheets",
                JOptionPane.YES_NO_CANCEL_OPTION,
                JOptionPane.QUESTION_MESSAGE);

        // If the user has bottled it then return false;
        if (option == JOptionPane.CANCEL_OPTION) {
            return false;
        }

        if (option == JOptionPane.YES_OPTION) {
            // If the user has opted to save then return the
            // status of the save operation (true => good)
            return saveTimesheets();
        } else {
            // The user doesn't want to save the changes; just abandon them
            // and let him proceed with whatever operation he has chosen
            return true;
        }
    }

    @Action(enabledProperty = "editsMade")
    public boolean saveTimesheets() {

        entityManager.getTransaction().begin();
        try {

            for (WeeklyTime timesheet : timesheetList) {

                // If the timesheet is already persistent then merge it
                if (entityManager.contains(timesheet)) {
                    entityManager.merge(timesheet);

                } else {
                    // If the timesheet is new then only persist it if not empty
                    if (!timesheet.isEmpty()) {
                        entityManager.persist(timesheet);
                    }
                }
            }

            entityManager.getTransaction().commit();
            setEditsMade(false);
            return true;
            
        } catch (RuntimeException e) {
            LOGGER.log(Level.SEVERE, "Error saving timesheets", e);
            ErrorDialog.showError(this, e);
            if (entityManager.getTransaction().isActive()) {
                entityManager.getTransaction().rollback();
            }
            return false;
        }
    }

    @Action
    public void displayAsHoursAndMinutes() {
        cellRenderer.showHoursAndMinutes(true);
        totalRenderer.showHoursAndMinutes(true);
        timesheetTable.repaint();
    }

    @Action
    public void displayAsHours() {
        cellRenderer.showHoursAndMinutes(false);
        totalRenderer.showHoursAndMinutes(false);
        timesheetTable.repaint();
    }
}